using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Routing;
using System.Web.Mvc;
using Nop.Core;
using Nop.Core.Domain;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Plugins;
using Nop.Plugin.Payments.PayPalExpress.Controllers;
using Nop.Plugin.Payments.PayPalExpress.PayPalSvc;
using Nop.Services.Common;
using Nop.Services.Configuration;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Discounts;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Tax;
using Nop.Services.Catalog;
using Nop.Core.Infrastructure;
using Nop.Services.Shipping;


namespace Nop.Plugin.Payments.PayPalExpress
{
    /// <summary>
    /// PayPalExpress payment processor
    /// </summary>
    public class PayPalExpressCheckoutPaymentProcessor : BasePlugin, IPaymentMethod
    {
        #region Fields

        private readonly PayPalExpressCheckoutPaymentSettings _paypalExpressCheckoutPaymentSettings;
        private readonly ISettingService _settingService;
        private readonly ICurrencyService _currencyService;
        private readonly CurrencySettings _currencySettings;
        private readonly IWebHelper _webHelper;
        private readonly StoreInformationSettings _storeInformationSettings;
        private readonly IStateProvinceService _stateProvinceService;
        private readonly ICountryService _countryService;
        private readonly ICustomerService _customerService;
        private readonly IWorkContext _workContext;
        private readonly IOrderTotalCalculationService _orderTotalCalculationService;
        private readonly IDiscountService _discountService;
        private readonly IPriceCalculationService _priceCalculationService;
        private readonly ITaxService _taxService;
        private readonly IPriceFormatter _priceFormatter;
        private readonly IShippingService _shippingService;
        private readonly IGenericAttributeService _genericAttributeService;
        private readonly IGiftCardService _giftCardService;

        decimal shoppingCartUnitPriceWithDiscount;

        #endregion

        #region Ctor

        public PayPalExpressCheckoutPaymentProcessor(PayPalExpressCheckoutPaymentSettings paypalExpressCheckoutPaymentSettings,
            ISettingService settingService, ICurrencyService currencyService,
            CurrencySettings currencySettings, IWebHelper webHelper,
            StoreInformationSettings storeInformationSettings,
            IStateProvinceService stateProvinceService,
            ICountryService countryService,
            ICustomerService customerService,
            IWorkContext workContext,
            IOrderTotalCalculationService orderTotalCalculationService,
            IDiscountService discountService,
            IPriceCalculationService priceCalculationService,
            ITaxService taxService,
            IPriceFormatter priceFormatter,
            IShippingService shippingService,
            IGenericAttributeService genericAttributeService,
            IGiftCardService giftCardService)
        {
            this._paypalExpressCheckoutPaymentSettings = paypalExpressCheckoutPaymentSettings;
            this._settingService = settingService;
            this._currencyService = currencyService;
            this._currencySettings = currencySettings;
            this._webHelper = webHelper;
            this._storeInformationSettings = storeInformationSettings;
            this._stateProvinceService = stateProvinceService;
            this._countryService = countryService;
            this._customerService = customerService;
            this._workContext = workContext;
            this._orderTotalCalculationService = orderTotalCalculationService;
            this._discountService = discountService;
            this._priceCalculationService = priceCalculationService;
            this._taxService = taxService;
            this._priceFormatter = priceFormatter;
            this._shippingService = shippingService;
            this._genericAttributeService = genericAttributeService;
            this._giftCardService = giftCardService;
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Gets Paypal URL
        /// </summary>
        /// <returns></returns>
        private string GetPaypalUrl()
        {
            return _paypalExpressCheckoutPaymentSettings.UseSandbox ? "https://www.sandbox.paypal.com/us/cgi-bin/webscr?useraction=commit" :
                "https://www.paypal.com/us/cgi-bin/webscr?useraction=commit";
        }

        /// <summary>
        /// Get Paypal country code
        /// </summary>
        /// <param name="country">Country</param>
        /// <returns>Paypal country code</returns>
        protected CountryCodeType GetPaypalCountryCodeType(Country country)
        {
            CountryCodeType payerCountry = CountryCodeType.US;
            try
            {
                payerCountry = (CountryCodeType)Enum.Parse(typeof(CountryCodeType), country.TwoLetterIsoCode);
            }
            catch
            {
            }
            return payerCountry;
        }

        /// <summary>
        /// Get Paypal credit card type
        /// </summary>
        /// <param name="CreditCardType">Credit card type</param>
        /// <returns>Paypal credit card type</returns>
        protected CreditCardTypeType GetPaypalCreditCardType(string CreditCardType)
        {
            CreditCardTypeType creditCardTypeType = (CreditCardTypeType)Enum.Parse(typeof(CreditCardTypeType), CreditCardType);
            return creditCardTypeType;
        }

        protected string GetApiVersion()
        {
            return "78";
        }

        /// <summary>
        /// Verifies IPN
        /// </summary>
        /// <param name="formString">Form string</param>
        /// <param name="values">Values</param>
        /// <returns>Result</returns>
        public bool VerifyIPN(string formString, out Dictionary<string, string> values)
        {
            var req = (HttpWebRequest)WebRequest.Create(GetPaypalUrl());
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";

            string formContent = string.Format("{0}&cmd=_notify-validate", formString);
            req.ContentLength = formContent.Length;

            using (var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII))
            {
                sw.Write(formContent);
            }

            string response = null;
            using (var sr = new StreamReader(req.GetResponse().GetResponseStream()))
            {
                response = HttpUtility.UrlDecode(sr.ReadToEnd());
            }
            bool success = response.Trim().Equals("VERIFIED", StringComparison.OrdinalIgnoreCase);

            values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            foreach (string l in formString.Split('&'))
            {
                string line = l.Trim();
                int equalPox = line.IndexOf('=');
                if (equalPox >= 0)
                    values.Add(line.Substring(0, equalPox), line.Substring(equalPox + 1));
            }

            return success;
        }

        #endregion

        #region Methods

        public GetExpressCheckoutDetailsResponseType GetExpressCheckoutDetails(string token)
        {
            var result = new GetExpressCheckoutDetailsResponseType();
            using (var service2 = new PayPalAPIAASoapBinding())
            {
                var req = new GetExpressCheckoutDetailsReq();
                req.GetExpressCheckoutDetailsRequest = new GetExpressCheckoutDetailsRequestType
                    {
                        Token = token,
                        Version = GetApiVersion()
                    };

                service2.Url = !_paypalExpressCheckoutPaymentSettings.UseSandbox ? "https://api-3t.paypal.com/2.0/" : "https://api-3t.sandbox.paypal.com/2.0/";

                service2.RequesterCredentials = new CustomSecurityHeaderType
                {
                    Credentials = new UserIdPasswordType
                    {
                        Username = _paypalExpressCheckoutPaymentSettings.ApiAccountName,
                        Password = _paypalExpressCheckoutPaymentSettings.ApiAccountPassword,
                        Signature = _paypalExpressCheckoutPaymentSettings.Signature,
                        Subject = ""
                    }
                };

                result = service2.GetExpressCheckoutDetails(req);
            }
            return result;
        }

        public bool CanRePostProcessPayment(Order order)
        {

            return false;
        }



        public DoExpressCheckoutPaymentResponseType DoExpressCheckoutPayment(ProcessPaymentRequest processPaymentRequest)
        {
            var result = new DoExpressCheckoutPaymentResponseType();

            // populate payment details
            var paymentDetails = new PaymentDetailsType
            {
                OrderTotal = new BasicAmountType
                {
                    Value = Math.Round(processPaymentRequest.OrderTotal, 2).ToString("N", new CultureInfo("en-us")),
                    currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId))
                },
                Custom = processPaymentRequest.OrderGuid.ToString(),
                ButtonSource = "nopCommerceCart"
            };

            // build the request
            var req = new DoExpressCheckoutPaymentReq
            {
                DoExpressCheckoutPaymentRequest = new DoExpressCheckoutPaymentRequestType
                {
                    Version = GetApiVersion(),
                    DoExpressCheckoutPaymentRequestDetails = new DoExpressCheckoutPaymentRequestDetailsType
                    {
                        Token = processPaymentRequest.PaypalToken,
                        PayerID = processPaymentRequest.PaypalPayerId,
                        PaymentAction = GetPaymentAction(),
                        PaymentActionSpecified = true,
                        PaymentDetails = new PaymentDetailsType[]
                        {
                            paymentDetails
                        }
                    }
                }
            };

            //execute request
            using (var service2 = new PayPalAPIAASoapBinding())
            {
                service2.Url = !_paypalExpressCheckoutPaymentSettings.UseSandbox ? "https://api-3t.paypal.com/2.0/" : "https://api-3t.sandbox.paypal.com/2.0/";

                service2.RequesterCredentials = new CustomSecurityHeaderType
                {
                    Credentials = new UserIdPasswordType
                    {
                        Username = _paypalExpressCheckoutPaymentSettings.ApiAccountName,
                        Password = _paypalExpressCheckoutPaymentSettings.ApiAccountPassword,
                        Signature = _paypalExpressCheckoutPaymentSettings.Signature,
                        Subject = ""
                    }
                };

                result = service2.DoExpressCheckoutPayment(req);
            }
            return result;
        }

        string GetItemNumber(ProductVariant productVariant)
        {
            if (String.IsNullOrEmpty(productVariant.Sku))
            {
                return productVariant.Id.ToString();
            }

            return productVariant.Sku;
        }

        public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentRequest processPaymentRequest, IList<Core.Domain.Orders.ShoppingCartItem> cart)
        {
            var result = new SetExpressCheckoutResponseType();

            // initialize ExpressCheckout object
            var req = new SetExpressCheckoutReq
                {
                    SetExpressCheckoutRequest = new SetExpressCheckoutRequestType
                        {
                            Version = GetApiVersion(),
                            SetExpressCheckoutRequestDetails = new SetExpressCheckoutRequestDetailsType()
                        }
                };

            // build ExpressCheckout details
            var details = new SetExpressCheckoutRequestDetailsType
                {
                    
                    PaymentAction = GetPaymentAction(),
                    PaymentActionSpecified = true,
                    CancelURL = _webHelper.GetStoreLocation(_webHelper.SslEnabled()) + "cart",
                    ReturnURL = _webHelper.GetStoreLocation(_webHelper.SslEnabled()) + "Plugins/PaymentPayPalExpress/GetDetails",
                    CallbackURL = _webHelper.GetStoreLocation(_webHelper.SslEnabled()) + "Plugins/PaymentPayPalExpress/ShippingOptions?CustomerID=" + _workContext.CurrentCustomer.Id.ToString(),
                    CallbackTimeout = _paypalExpressCheckoutPaymentSettings.CallbackTimeout.ToString() //this is the amount of seconds PayPal will wait for our shipping options before it uses the default shipping value specified below
                };

            // populate cart
            decimal itemTotal = decimal.Zero;
            var cartItems = new List<PaymentDetailsItemType>();
            foreach (ShoppingCartItem item in cart)
            {
                decimal shoppingCartUnitPriceWithDiscountBase = _priceCalculationService.GetUnitPrice(item, true);
                shoppingCartUnitPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartUnitPriceWithDiscountBase, _workContext.WorkingCurrency);
                decimal priceIncludingTier = shoppingCartUnitPriceWithDiscount;          
                cartItems.Add(new PaymentDetailsItemType()
                         {
                             Name = item.ProductVariant.FullProductName,
                             Number = GetItemNumber(item.ProductVariant),
                             Quantity = item.Quantity.ToString(),
                             Amount = new BasicAmountType()  // this is the per item cost
                             {
                                 currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)),
                                 Value = (priceIncludingTier).ToString("N", new CultureInfo("en-us"))
                             }
                         });
                itemTotal += (item.Quantity * priceIncludingTier);
            };

            decimal shippingTotal = decimal.Zero;
            if (cart.RequiresShipping())
            {
                decimal? shoppingCartShippingBase = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart);
                if (shoppingCartShippingBase.HasValue)
                {
                    shippingTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartShippingBase.Value, _workContext.WorkingCurrency);
                }
                else
                {
                    shippingTotal = _paypalExpressCheckoutPaymentSettings.DefaultShippingPrice;
                }
            }

            //This is the default if the Shipping Callback fails
            var shippingOptions = new List<ShippingOptionType>();
            shippingOptions.Add(new ShippingOptionType()
                {
                    ShippingOptionIsDefault = "true",
                    ShippingOptionName = "Standard Shipping",
                    ShippingOptionAmount = new BasicAmountType()
                    {
                        Value = shippingTotal.ToString(), //This is the default value used for shipping if the Instant Update API returns an error or does not answer within the callback time
                        currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId))
                    }
                });
            details.FlatRateShippingOptions = shippingOptions.ToArray();
            details.TotalType = TotalType.EstimatedTotal;

            // get total tax
            SortedDictionary<decimal, decimal> taxRates = null;
            decimal shoppingCartTaxBase = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates);
            decimal shoppingCartTax = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTaxBase, _workContext.WorkingCurrency);
            decimal discount = -processPaymentRequest.Discount;


            // Add discounts to PayPal order
            if (discount != 0)
            {
                cartItems.Add(new PaymentDetailsItemType()
                {
                    Name = "Threadrock Discount",
                    Quantity = "1",
                    Amount = new BasicAmountType() // this is the total discount
                    {
                        currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)),
                        Value = discount.ToString("N", new CultureInfo("en-us"))
                    }
                });

                itemTotal += discount;
            }

            // get customer
            int customerId = Convert.ToInt32(_workContext.CurrentCustomer.Id.ToString());
            var customer = _customerService.GetCustomerById(customerId);

            if (!cart.IsRecurring())
            {
                //we don't apply gift cards for recurring products
                var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer);
                if (giftCards != null)
                {
                    foreach (var gc in giftCards)
                    {
                        if (itemTotal > decimal.Zero)
                        {
                            decimal remainingAmount = gc.GetGiftCardRemainingAmount();
                            decimal amountCanBeUsed = decimal.Zero;
                            if (itemTotal > remainingAmount)
                                amountCanBeUsed = remainingAmount;
                            else
                                amountCanBeUsed = itemTotal - .01M;

                            decimal amountToSubtract = -amountCanBeUsed;

                            cartItems.Add(new PaymentDetailsItemType()
                            {
                                Name = "Giftcard Applied",
                                Quantity = "1",
                                Amount = new BasicAmountType() 
                                {
                                    currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)),
                                    Value = amountToSubtract.ToString("N", new CultureInfo("en-us"))
                                }
                            });

                            //reduce subtotal
                            itemTotal += amountToSubtract;
                        }
                    }
                }
            }

            // populate payment details
            var paymentDetails = new PaymentDetailsType
            {
                ItemTotal = new BasicAmountType
                {
                    Value = Math.Round(itemTotal, 2).ToString("N", new CultureInfo("en-us")),
                    currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId))
                },
                ShippingTotal = new BasicAmountType
                {
                    Value = Math.Round(shippingTotal, 2).ToString("N", new CultureInfo("en-us")),
                    currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId))
                },
                TaxTotal = new BasicAmountType
                {
                    Value = Math.Round(shoppingCartTax, 2).ToString("N", new CultureInfo("en-us")),
                    currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId))
                },
                OrderTotal = new BasicAmountType
                {
                    Value = Math.Round(itemTotal + shoppingCartTax + shippingTotal, 2).ToString("N", new CultureInfo("en-us")),
                    //Value = Math.Round(processPaymentRequest.OrderTotal, 2).ToString("N", new CultureInfo("en-us")),
                    currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId))
                },
                Custom = processPaymentRequest.OrderGuid.ToString(),
                ButtonSource = "nopCommerceCart",
                PaymentAction = GetPaymentAction(),
                PaymentDetailsItem = cartItems.ToArray()
            };
            details.PaymentDetails = new[] { paymentDetails };
            details.MaxAmount = new BasicAmountType()  // this is the per item cost
                {
                    currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)),
                    Value = (decimal.Parse(paymentDetails.OrderTotal.Value) + 30).ToString()
                };

            req.SetExpressCheckoutRequest.SetExpressCheckoutRequestDetails.Custom = processPaymentRequest.OrderGuid.ToString();
            req.SetExpressCheckoutRequest.SetExpressCheckoutRequestDetails = details;
            
            using (var service2 = new PayPalAPIAASoapBinding())
            {
                service2.Url = !_paypalExpressCheckoutPaymentSettings.UseSandbox ? "https://api-3t.paypal.com/2.0/" : "https://api-3t.sandbox.paypal.com/2.0/";

                service2.RequesterCredentials = new CustomSecurityHeaderType
                    {
                        Credentials = new UserIdPasswordType
                            {
                                Username = _paypalExpressCheckoutPaymentSettings.ApiAccountName,
                                Password = _paypalExpressCheckoutPaymentSettings.ApiAccountPassword,
                                Signature = _paypalExpressCheckoutPaymentSettings.Signature,
                                Subject = ""
                            }
                    };

                result = service2.SetExpressCheckout(req);
            }
            return result;
        }

        private PaymentActionCodeType GetPaymentAction()
        {
            if (_paypalExpressCheckoutPaymentSettings.TransactMode == TransactMode.Authorize)
            {
                return PaymentActionCodeType.Authorization;
            }
            else
            {
                return PaymentActionCodeType.Sale;
            }
        }

        public decimal GetOrderTotal(decimal itemTotal, decimal shippingTotal, decimal salesTax)
        {
            return itemTotal + shippingTotal + salesTax;
        }

        public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPaymentRequest, GetExpressCheckoutDetailsResponseDetailsType checkoutDetails)
        {
            // get customer & cart
            //var customer = customer;
            int customerId = Convert.ToInt32(_workContext.CurrentCustomer.Id.ToString());
            var customer = _customerService.GetCustomerById(customerId);

            _workContext.CurrentCustomer = customer;

            var cart = customer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart).ToList();

            // get/update billing address
            string billingFirstName = checkoutDetails.PayerInfo.PayerName.FirstName;
            string billingLastName = checkoutDetails.PayerInfo.PayerName.LastName;
            string billingEmail = checkoutDetails.PayerInfo.Payer;
            string billingAddress1 = checkoutDetails.PayerInfo.Address.Street1;
            string billingAddress2 = checkoutDetails.PayerInfo.Address.Street2;
            string billingPhoneNumber = checkoutDetails.PayerInfo.ContactPhone;
            string billingCity = checkoutDetails.PayerInfo.Address.CityName;
            int? billingStateProvinceId = null;
            var billingStateProvince = _stateProvinceService.GetStateProvinceByAbbreviation(checkoutDetails.PayerInfo.Address.StateOrProvince);
            if (billingStateProvince != null)
                billingStateProvinceId = billingStateProvince.Id;
            string billingZipPostalCode = checkoutDetails.PayerInfo.Address.PostalCode;
            int? billingCountryId = null;
            var billingCountry = _countryService.GetCountryByTwoLetterIsoCode(checkoutDetails.PayerInfo.Address.Country.ToString());
            if (billingCountry != null)
                billingCountryId = billingCountry.Id;

            var billingAddress = customer.Addresses.ToList().FindAddress(
                billingFirstName, billingLastName, billingPhoneNumber,
                billingEmail, string.Empty, string.Empty, billingAddress1, billingAddress2, billingCity,
                billingStateProvinceId, billingZipPostalCode, billingCountryId);

            if (billingAddress == null)
            {
                billingAddress = new Core.Domain.Common.Address()
                {
                    FirstName = billingFirstName,
                    LastName = billingLastName,
                    PhoneNumber = billingPhoneNumber,
                    Email = billingEmail,
                    FaxNumber = string.Empty,
                    Company =  string.Empty,
                    Address1 = billingAddress1,
                    Address2 = billingAddress2,
                    City = billingCity,
                    StateProvinceId = billingStateProvinceId,
                    ZipPostalCode = billingZipPostalCode,
                    CountryId = billingCountryId,
                    CreatedOnUtc = DateTime.UtcNow,
                };
                customer.Addresses.Add(billingAddress);
            }

            //set default billing address
            customer.BillingAddress = billingAddress;
            _customerService.UpdateCustomer(customer);

            var genericAttributeService = EngineContext.Current.Resolve<IGenericAttributeService>();
            genericAttributeService.SaveAttribute<ShippingOption>(customer, SystemCustomerAttributeNames.LastShippingOption, null);

            bool shoppingCartRequiresShipping = cart.RequiresShipping();
            if (shoppingCartRequiresShipping)
            {
                var paymentDetails = checkoutDetails.PaymentDetails.FirstOrDefault();
                string[] shippingFullname = paymentDetails.ShipToAddress.Name.Trim().Split(new char[] { ' ' }, 2,
                                                                                            StringSplitOptions.
                                                                                                RemoveEmptyEntries);
                string shippingFirstName = shippingFullname[0];
                string shippingLastName = string.Empty;
                if (shippingFullname.Length > 1)
                    shippingLastName = shippingFullname[1];
                string shippingEmail = checkoutDetails.PayerInfo.Payer;
                string shippingAddress1 = paymentDetails.ShipToAddress.Street1;
                string shippingAddress2 = paymentDetails.ShipToAddress.Street2;
                string shippingPhoneNumber = paymentDetails.ShipToAddress.Phone;
                string shippingCity = paymentDetails.ShipToAddress.CityName;
                int? shippingStateProvinceId = null;
                var shippingStateProvince =
                    _stateProvinceService.GetStateProvinceByAbbreviation(
                        paymentDetails.ShipToAddress.StateOrProvince);
                if (shippingStateProvince != null)
                    shippingStateProvinceId = shippingStateProvince.Id;
                int? shippingCountryId = null;
                string shippingZipPostalCode = paymentDetails.ShipToAddress.PostalCode;
                var shippingCountry =
                    _countryService.GetCountryByTwoLetterIsoCode(paymentDetails.ShipToAddress.Country.ToString());
                if (shippingCountry != null)
                    shippingCountryId = shippingCountry.Id;

                var shippingAddress = customer.Addresses.ToList().FindAddress(
                    shippingFirstName, shippingLastName, shippingPhoneNumber,
                    shippingEmail, string.Empty, string.Empty,
                    shippingAddress1, shippingAddress2, shippingCity,
                    shippingStateProvinceId, shippingZipPostalCode, shippingCountryId);

                if (shippingAddress == null)
                {
                     shippingAddress = new Core.Domain.Common.Address()
                                            {
                                                FirstName = shippingFirstName,
                                                LastName = shippingLastName,
                                                PhoneNumber = shippingPhoneNumber,
                                                Email = shippingEmail,
                                                FaxNumber = string.Empty,
                                                Company =  string.Empty,
                                                Address1 = shippingAddress1,
                                                Address2 = shippingAddress2,
                                                City = shippingCity,
                                                StateProvinceId = shippingStateProvinceId,
                                                ZipPostalCode = shippingZipPostalCode,
                                                CountryId = shippingCountryId,
                                                CreatedOnUtc = DateTime.UtcNow,
                                            };
                   customer.Addresses.Add(shippingAddress);
                }
                
                //set default shipping address
                customer.ShippingAddress = shippingAddress;
                _customerService.UpdateCustomer(customer);
            }

            bool isShippingSet = false;
            GetShippingOptionResponse getShippingOptionResponse = _shippingService.GetShippingOptions(cart, customer.ShippingAddress);
            if (getShippingOptionResponse.Success && getShippingOptionResponse.ShippingOptions.Count > 0)
            {
                foreach (var shippingOption in getShippingOptionResponse.ShippingOptions)
                {
                    if (checkoutDetails.UserSelectedOptions.ShippingOptionName.Contains(shippingOption.Name) && 
                        checkoutDetails.UserSelectedOptions.ShippingOptionName.Contains(shippingOption.Description))
                    {
                        _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.LastShippingOption, shippingOption);
                        isShippingSet = true;
                        break;
                    }
                }
            }

            if (!isShippingSet)
            {
                var shippingOption = new ShippingOption();
                shippingOption.Name = checkoutDetails.UserSelectedOptions.ShippingOptionName;
                decimal shippingPrice = _paypalExpressCheckoutPaymentSettings.DefaultShippingPrice;
                decimal.TryParse(checkoutDetails.UserSelectedOptions.ShippingOptionAmount.Value, out shippingPrice);
                shippingOption.Rate = shippingPrice;
                _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastShippingOption, shippingOption);
            }

            processPaymentRequest.PaypalPayerId = checkoutDetails.PayerInfo.PayerID;
            return processPaymentRequest;
        }

        /// <summary>
        /// Process a payment
        /// </summary>
        /// <param name="processPaymentRequest">Payment info required for an order processing</param>
        /// <returns>Process payment result</returns>
        public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest)
        {
            var doPayment = DoExpressCheckoutPayment(processPaymentRequest);
            var result = new ProcessPaymentResult();

            if (doPayment.Ack == AckCodeType.Success)
            {
                if (GetPaymentAction() == PaymentActionCodeType.Authorization)
                {
                    result.NewPaymentStatus = PaymentStatus.Authorized;
                }
                else
                {
                    result.NewPaymentStatus = PaymentStatus.Paid;
                }
                result.AuthorizationTransactionId = processPaymentRequest.PaypalToken;
                result.CaptureTransactionId = doPayment.DoExpressCheckoutPaymentResponseDetails.PaymentInfo.FirstOrDefault().TransactionID;
                result.CaptureTransactionResult = doPayment.Ack.ToString();
            }
            else
            {
                result.NewPaymentStatus = PaymentStatus.Pending;
            }

            return result;
        }

        /// <summary>
        /// Post process payment (used by payment gateways that require reExpressing to a third-party URL)
        /// </summary>
        /// <param name="postProcessPaymentRequest">Payment info required for an order processing</param>
        public void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest)
        {
        }

        /// <summary>
        /// Gets additional handling fee
        /// </summary>
        /// <returns>Additional handling fee</returns>
        public decimal GetAdditionalHandlingFee()
        {
            return _paypalExpressCheckoutPaymentSettings.AdditionalFee;
        }

        /// <summary>
        /// Captures payment
        /// </summary>
        /// <param name="capturePaymentRequest">Capture payment request</param>
        /// <returns>Capture payment result</returns>
        public CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest)
        {
            var result = new CapturePaymentResult();
            return result;
        }

        /// <summary>
        /// Refunds a payment
        /// </summary>
        /// <param name="refundPaymentRequest">Request</param>
        /// <returns>Result</returns>
        public RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest)
        {
            var result = new RefundPaymentResult();

            string transactionId = refundPaymentRequest.Order.CaptureTransactionId;

            var req = new RefundTransactionReq();
            req.RefundTransactionRequest = new RefundTransactionRequestType();
            //NOTE: Specify amount in partial refund
            req.RefundTransactionRequest.RefundType = RefundType.Full;
            req.RefundTransactionRequest.RefundTypeSpecified = true;
            req.RefundTransactionRequest.Version = GetApiVersion();
            req.RefundTransactionRequest.TransactionID = transactionId;

            using (var service1 = new PayPalAPISoapBinding())
            {
                if (!_paypalExpressCheckoutPaymentSettings.UseSandbox)
                    service1.Url = "https://api-3t.paypal.com/2.0/";
                else
                    service1.Url = "https://api-3t.sandbox.paypal.com/2.0/";

                service1.RequesterCredentials = new CustomSecurityHeaderType();
                service1.RequesterCredentials.Credentials = new UserIdPasswordType();
                service1.RequesterCredentials.Credentials.Username = _paypalExpressCheckoutPaymentSettings.ApiAccountName;
                service1.RequesterCredentials.Credentials.Password = _paypalExpressCheckoutPaymentSettings.ApiAccountPassword;
                service1.RequesterCredentials.Credentials.Signature = _paypalExpressCheckoutPaymentSettings.Signature;
                service1.RequesterCredentials.Credentials.Subject = "";

                RefundTransactionResponseType response = service1.RefundTransaction(req);

                string error = string.Empty;
                bool Success = PaypalHelper.CheckSuccess(response, out error);
                if (Success)
                {
                    result.NewPaymentStatus = PaymentStatus.Refunded;
                    //cancelPaymentResult.RefundTransactionID = response.RefundTransactionID;
                }
                else
                {
                    result.AddError(error);
                }
            }

            return result;
        }

        /// <summary>
        /// Voids a payment
        /// </summary>
        /// <param name="voidPaymentRequest">Request</param>
        /// <returns>Result</returns>
        public VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest)
        {
            var result = new VoidPaymentResult();

            string transactionId = voidPaymentRequest.Order.AuthorizationTransactionId;
            if (String.IsNullOrEmpty(transactionId))
                transactionId = voidPaymentRequest.Order.CaptureTransactionId;

            var req = new DoVoidReq();
            req.DoVoidRequest = new DoVoidRequestType();
            req.DoVoidRequest.Version = GetApiVersion();
            req.DoVoidRequest.AuthorizationID = transactionId;


            using (var service2 = new PayPalAPIAASoapBinding())
            {
                if (!_paypalExpressCheckoutPaymentSettings.UseSandbox)
                    service2.Url = "https://api-3t.paypal.com/2.0/";
                else
                    service2.Url = "https://api-3t.sandbox.paypal.com/2.0/";

                service2.RequesterCredentials = new CustomSecurityHeaderType();
                service2.RequesterCredentials.Credentials = new UserIdPasswordType();
                service2.RequesterCredentials.Credentials.Username = _paypalExpressCheckoutPaymentSettings.ApiAccountName;
                service2.RequesterCredentials.Credentials.Password = _paypalExpressCheckoutPaymentSettings.ApiAccountPassword;
                service2.RequesterCredentials.Credentials.Signature = _paypalExpressCheckoutPaymentSettings.Signature;
                service2.RequesterCredentials.Credentials.Subject = "";

                DoVoidResponseType response = service2.DoVoid(req);

                string error = "";
                bool success = PaypalHelper.CheckSuccess(response, out error);
                if (success)
                {
                    result.NewPaymentStatus = PaymentStatus.Voided;
                    //result.VoidTransactionID = response.RefundTransactionID;
                }
                else
                {
                    result.AddError(error);
                }
            }
            return result;
        }

        /// <summary>
        /// Process recurring payment
        /// </summary>
        /// <param name="processPaymentRequest">Payment info required for an order processing</param>
        /// <returns>Process payment result</returns>
        public ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest)
        {
            ICustomerService customerService = EngineContext.Current.Resolve<ICustomerService>();
            Customer customer = customerService.GetCustomerById(processPaymentRequest.CustomerId);

            var result = new ProcessPaymentResult();

            var req = new CreateRecurringPaymentsProfileReq();
            req.CreateRecurringPaymentsProfileRequest = new CreateRecurringPaymentsProfileRequestType();
            req.CreateRecurringPaymentsProfileRequest.Version = GetApiVersion();
            var details = new CreateRecurringPaymentsProfileRequestDetailsType();
            req.CreateRecurringPaymentsProfileRequest.CreateRecurringPaymentsProfileRequestDetails = details;

            details.CreditCard = new CreditCardDetailsType();
            details.CreditCard.CreditCardNumber = processPaymentRequest.CreditCardNumber;
            details.CreditCard.CreditCardType = GetPaypalCreditCardType(processPaymentRequest.CreditCardType);
            details.CreditCard.ExpMonthSpecified = true;
            details.CreditCard.ExpMonth = processPaymentRequest.CreditCardExpireMonth;
            details.CreditCard.ExpYearSpecified = true;
            details.CreditCard.ExpYear = processPaymentRequest.CreditCardExpireYear;
            details.CreditCard.CVV2 = processPaymentRequest.CreditCardCvv2;
            details.CreditCard.CardOwner = new PayerInfoType();
            details.CreditCard.CardOwner.PayerCountry = GetPaypalCountryCodeType(customer.BillingAddress.Country);
            details.CreditCard.CreditCardTypeSpecified = true;

            details.CreditCard.CardOwner.Address = new AddressType();
            details.CreditCard.CardOwner.Address.CountrySpecified = true;
            details.CreditCard.CardOwner.Address.Street1 = customer.BillingAddress.Address1;
            details.CreditCard.CardOwner.Address.Street2 = customer.BillingAddress.Address2;
            details.CreditCard.CardOwner.Address.CityName = customer.BillingAddress.City;
            if (customer.BillingAddress.StateProvince != null)
                details.CreditCard.CardOwner.Address.StateOrProvince = customer.BillingAddress.StateProvince.Abbreviation;
            else
                details.CreditCard.CardOwner.Address.StateOrProvince = "CA";
            details.CreditCard.CardOwner.Address.Country = GetPaypalCountryCodeType(customer.BillingAddress.Country);
            details.CreditCard.CardOwner.Address.PostalCode = customer.BillingAddress.ZipPostalCode;
            details.CreditCard.CardOwner.Payer = customer.BillingAddress.Email;
            details.CreditCard.CardOwner.PayerName = new PersonNameType();
            details.CreditCard.CardOwner.PayerName.FirstName = customer.BillingAddress.FirstName;
            details.CreditCard.CardOwner.PayerName.LastName = customer.BillingAddress.LastName;

            //start date
            details.RecurringPaymentsProfileDetails = new RecurringPaymentsProfileDetailsType();
            details.RecurringPaymentsProfileDetails.BillingStartDate = DateTime.UtcNow;
            details.RecurringPaymentsProfileDetails.ProfileReference = processPaymentRequest.OrderGuid.ToString();

            //schedule
            details.ScheduleDetails = new ScheduleDetailsType();
            details.ScheduleDetails.Description = string.Format("{0} - {1}", _storeInformationSettings.StoreName, "recurring payment");
            details.ScheduleDetails.PaymentPeriod = new BillingPeriodDetailsType();
            details.ScheduleDetails.PaymentPeriod.Amount = new BasicAmountType();
            details.ScheduleDetails.PaymentPeriod.Amount.Value = Math.Round(processPaymentRequest.OrderTotal, 2).ToString("N", new CultureInfo("en-us"));
            details.ScheduleDetails.PaymentPeriod.Amount.currencyID = PaypalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId));
            details.ScheduleDetails.PaymentPeriod.BillingFrequency = processPaymentRequest.RecurringCycleLength;
            switch (processPaymentRequest.RecurringCyclePeriod)
            {
                case RecurringProductCyclePeriod.Days:
                    details.ScheduleDetails.PaymentPeriod.BillingPeriod = BillingPeriodType.Day;
                    break;
                case RecurringProductCyclePeriod.Weeks:
                    details.ScheduleDetails.PaymentPeriod.BillingPeriod = BillingPeriodType.Week;
                    break;
                case RecurringProductCyclePeriod.Months:
                    details.ScheduleDetails.PaymentPeriod.BillingPeriod = BillingPeriodType.Month;
                    break;
                case RecurringProductCyclePeriod.Years:
                    details.ScheduleDetails.PaymentPeriod.BillingPeriod = BillingPeriodType.Year;
                    break;
                default:
                    throw new NopException("Not supported cycle period");
            }
            details.ScheduleDetails.PaymentPeriod.TotalBillingCycles = processPaymentRequest.RecurringTotalCycles;
            details.ScheduleDetails.PaymentPeriod.TotalBillingCyclesSpecified = true;



            using (var service2 = new PayPalAPIAASoapBinding())
            {
                if (!_paypalExpressCheckoutPaymentSettings.UseSandbox)
                    service2.Url = "https://api-3t.paypal.com/2.0/";
                else
                    service2.Url = "https://api-3t.sandbox.paypal.com/2.0/";

                service2.RequesterCredentials = new CustomSecurityHeaderType();
                service2.RequesterCredentials.Credentials = new UserIdPasswordType();
                service2.RequesterCredentials.Credentials.Username = _paypalExpressCheckoutPaymentSettings.ApiAccountName;
                service2.RequesterCredentials.Credentials.Password = _paypalExpressCheckoutPaymentSettings.ApiAccountPassword;
                service2.RequesterCredentials.Credentials.Signature = _paypalExpressCheckoutPaymentSettings.Signature;
                service2.RequesterCredentials.Credentials.Subject = "";

                CreateRecurringPaymentsProfileResponseType response = service2.CreateRecurringPaymentsProfile(req);

                string error = "";
                bool success = PaypalHelper.CheckSuccess(response, out error);
                if (success)
                {
                    result.NewPaymentStatus = PaymentStatus.Pending;
                    if (response.CreateRecurringPaymentsProfileResponseDetails != null)
                    {
                        result.SubscriptionTransactionId = response.CreateRecurringPaymentsProfileResponseDetails.ProfileID;
                    }
                }
                else
                {
                    result.AddError(error);
                }
            }

            return result;
        }

        /// <summary>
        /// Cancels a recurring payment
        /// </summary>
        /// <param name="cancelPaymentRequest">Request</param>
        /// <returns>Result</returns>
        public CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest)
        {
            //always success (cancel only on PayPal site)
            return new CancelRecurringPaymentResult();
        }

        /// <summary>
        /// Gets a route for provider configuration
        /// </summary>
        /// <param name="actionName">Action name</param>
        /// <param name="controllerName">Controller name</param>
        /// <param name="routeValues">Route values</param>
        public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues)
        {
            actionName = "Configure";
            controllerName = "PaymentPayPalExpress";
            routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.PayPalExpress.Controllers" }, { "area", null } };
        }

        public string GetInstantUpdateShippingOptions(string input, Customer customer)
        {

            var decoder = new NVPCodec();
            var encoder = new NVPCodec();

            decoder.Decode(input);

            string currencyCode = decoder["CURRENCYCODE"];

            _workContext.CurrentCustomer = customer;
            var cart = customer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart).ToList();

            int? shippingStateProvinceId = null;
            var shippingStateProvince =
                _stateProvinceService.GetStateProvinceByAbbreviation(
                decoder["SHIPTOSTATE"]);
            if (shippingStateProvince != null)
                shippingStateProvinceId = shippingStateProvince.Id;
            int? shippingCountryId = null;
            string shippingZipPostalCode = decoder["SHIPTOZIP"];
            var shippingCountry =
                _countryService.GetCountryByTwoLetterIsoCode(decoder["SHIPTOCOUNTRY"]);
            if (shippingCountry != null)
                shippingCountryId = shippingCountry.Id;

            if (cart.RequiresShipping())
            {
                var address = new Address()
                {
                    CountryId = shippingCountryId,
                    Country = shippingCountry,
                    StateProvinceId = shippingStateProvinceId,
                    StateProvince = shippingStateProvince,
                    ZipPostalCode = shippingZipPostalCode
                };

                GetShippingOptionResponse getShippingOptionResponse = _shippingService.GetShippingOptions(cart, address);
                if (getShippingOptionResponse.Success && getShippingOptionResponse.ShippingOptions.Count > 0)
                {
                    encoder["METHOD"] = "CallbackResponse";
                    int count = 0;
                    decimal tax = _orderTotalCalculationService.GetTaxTotal(cart);
                    foreach (var shippingOption in getShippingOptionResponse.ShippingOptions)
                    {
                        encoder["L_SHIPPINGOPTIONNAME" + count] = shippingOption.Name;
                        encoder["L_SHIPPINGOPTIONLABEL" + count] = shippingOption.Description;
                        encoder["L_SHIPPINGOPTIONAMOUNT" + count] = shippingOption.Rate.ToString();
                        encoder["L_SHIPPINGOPTIONISDEFAULT" + count] = (count == 0 ? "true" : "false");

                        if (tax > 0)
                        {
                            encoder["L_TAXAMT" + count] = tax.ToString();
                        }

                        count++;
                    }
                }
            }

            return encoder.Encode();
        }

        /// <summary>
        /// Gets a route for payment info
        /// </summary>
        /// <param name="actionName">Action name</param>
        /// <param name="controllerName">Controller name</param>
        /// <param name="routeValues">Route values</param>
        public void GetPaymentInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues)
        {
            actionName = "PaymentInfo";
            controllerName = "PaymentPayPalExpress";
            routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.PayPalExpress.Controllers" }, { "area", null } };
        }

        public Type GetControllerType()
        {
            return typeof(PaymentPayPalExpressController);
        }

        public override void Install()
        {
            var settings = new PayPalExpressCheckoutPaymentSettings()
            {
                TransactMode = TransactMode.Authorize,
                UseSandbox = true,
                CallbackTimeout = 4,
            };
            _settingService.SaveSetting(settings);

            base.Install();
        }

        #endregion

        #region Properies

        /// <summary>
        /// Gets a value indicating whether capture is supported
        /// </summary>
        public bool SupportCapture
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a value indicating whether partial refund is supported
        /// </summary>
        public bool SupportPartiallyRefund
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets a value indicating whether refund is supported
        /// </summary>
        public bool SupportRefund
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a value indicating whether void is supported
        /// </summary>
        public bool SupportVoid
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets a recurring payment type of payment method
        /// </summary>
        public RecurringPaymentType RecurringPaymentType
        {
            get
            {
                return RecurringPaymentType.Automatic;
            }
        }

        /// <summary>
        /// Gets a payment method type
        /// </summary>
        public PaymentMethodType PaymentMethodType
        {
            get
            {
                return PaymentMethodType.Button;
            }
        }

        #endregion
    }
}